package com.btcode.web.core.server;

import com.btcode.reflect.AutoClassLoader;
import com.btcode.reflect.IClassFinder;
import com.btcode.reflect.IClassLoader;
import com.btcode.web.core.controller.ControllerFinder;
import com.btcode.web.core.filter.WebContextFilter;
import com.btcode.web.core.filter.WebFilter;
import com.btcode.web.core.servlet.DefaultResourceNotFoundHandlerServlet;
import com.btcode.web.core.servlet.UrlMappingServlet;
import com.btcode.web.core.servlet.WebServlet;
import com.btcode.web.core.unit.URLUtil;
import com.btcode.web.freemarker.directive.FileListDirective;
import com.btcode.web.freemarker.servlet.FreemarkerHandleServlet;
import com.btcode.web.mvc.errorhandle.ApiErrorHandler;
import com.btcode.web.mvc.servlet.ApiPackScanInfo;
import com.btcode.web.mvc.servlet.ApiUrlHandleServlet;
import com.btcode.web.safe.filter.EntranceAndLogin;
import com.btcode.web.safe.filter.UserIdentityFilter;
import com.btcode.web.safe.filter.XSSCheckFilter;
import com.btcode.web.safe.service.IVerifyService;
import com.btcode.web.upload.servlet.FileUploadServlet;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.Servlet;
import org.eclipse.jetty.server.NCSARequestLog;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;

/**
 * 默认的jetty服务，提供基础的地址映射、freemarker支持、静态资源访问支持 欢迎页面，也就是起始页面 登陆页面，也就是session无效跳转过去的页面 错误页面 资源不存在页面
 *
 * @author gislin
 */
public class DefaultServer {

    private int port;

    private String logPath;

    private String resourceBasePath;

    private List<ApiPackScanInfo> controllerPackets;

    private String projectName;

    /**
     * 是否启用url映射检查
     */
    private boolean isUrlMappingCheck = false;

    /**
     * filter集合
     */
    @SuppressWarnings("rawtypes")
    private List<WebFilter> filters = new ArrayList<>();

    /**
     * servlets集合
     */
    private List<WebServlet> servlets = new ArrayList<>();

    /**
     * |--project |----src |----webResource |----js |----view
     *
     * @param port 端口
     * @param logPath 日志路径，用于记录jetty本身的日志
     * @param resourceBasePath web资源的起始目录，静态资源和freemarker页面依赖这个路径
     * @param controllerPackets web的包
     * @param projectName 项目名称，项目访问的时候，端口后面第一级的名字，例如http://localhost:8088/projectName/a/b/c
     */
    public DefaultServer(int port, String logPath, String resourceBasePath,
        List<ApiPackScanInfo> controllerPackets, String projectName) {
        this.port = port;
        this.logPath = logPath;
        this.resourceBasePath = resourceBasePath;
        this.controllerPackets = controllerPackets;
        setProjectName(projectName);
        URLUtil.setProjectName(projectName);
    }

    /**
     * 添加filter
     */
    public <T extends Filter> void addFilter(Class<T> filterClass, String path,
        HashMap<String, String> initParams) {
        WebFilter<T> filter = new WebFilter<T>(path, filterClass, initParams);
        filters.add(filter);
    }

    /**
     * 添加servlet
     */
    public void addServlet(Servlet servlet, String path) {
        WebServlet webServlet = new WebServlet(path, servlet);
        servlets.add(webServlet);
    }

    /**
     * 启用欢迎页面servlet 欢迎页面的地址，访问http://localhost:8088/projectName/index.html对应的页面,采用服务端跳转，对用户访问更加友好
     */
    public void enabledWelcomeServlet(String url) {
        UrlMappingServlet welcomeServlet = new UrlMappingServlet(url);
        WebServlet servlet = new WebServlet("/index.html", welcomeServlet);
        servlets.add(servlet);
    }

    /**
     * 映射地址
     *
     * @param accectUrl 访问地址
     * @param targetUrl 原地址
     */
    public void addUrlMapping(String accectUrl, String targetUrl) {
        UrlMappingServlet welcomeServlet = new UrlMappingServlet(targetUrl);
        WebServlet servlet = new WebServlet(accectUrl, welcomeServlet);
        servlets.add(servlet);
    }

    /**
     * 启用身份验证，单入口
     *
     * @param loginPageUrl 登录页面的地址，当未登录状态，尝试访问系统时，跳转到这个地址
     * @param verifyService 请求验证service，校验每个请求是否可以通过
     */
    public void enableAuthentication(String loginPageUrl, IVerifyService verifyService) {

        List<EntranceAndLogin> entrances = new ArrayList<>();
        EntranceAndLogin entrance = new EntranceAndLogin("/", loginPageUrl);
        entrances.add(entrance);
        enableAuthentication(entrances, verifyService);
    }

    /**
     * 启用身份验证,多入口
     *
     * @param entrances 入口信息，用于处理一个系统有多个登录界面的场景，比如一个平台登录界面，一个手机登录界面
     * @param verifyService 请求验证service，校验每个请求是否可以通过
     */
    public void enableAuthentication(List<EntranceAndLogin> entrances, IVerifyService verifyService) {

        UserIdentityFilter authenticationFilter = new UserIdentityFilter(projectName);
        authenticationFilter.addEntrances(entrances);
        authenticationFilter.setVerifyService(verifyService);
        WebFilter<?> filter = new WebFilter<>("/*", authenticationFilter,null);
        filters.add(filter);
    }

    /**
     * 启用文件上传支持
     *
     * @param fileUploadBasePath 文件上传的根目录
     */
    public void enableFileUpload(String fileUploadBasePath) throws URISyntaxException {
        enableFileUpload("/upload",fileUploadBasePath);
    }

    /**
     * 启用文件上传支持,可自定义访问路径
     * @param urlPrifix 上传路径前缀
     * @param fileUploadBasePath 文件上传的根目录
     * @throws URISyntaxException
     */
    public void enableFileUpload(String urlPrifix,String fileUploadBasePath) throws URISyntaxException {
        FileUploadServlet fileUploadServlet = new FileUploadServlet(fileUploadBasePath, urlPrifix);
        WebServlet servlet = new WebServlet(urlPrifix+"/*", fileUploadServlet);
        servlets.add(servlet);
    }

    /**
     * 启用url映射配置检查
     */
    public void enableUrlMappingCheck() {
        isUrlMappingCheck = true;
    }

    /**
     * 启动jetty服务
     */
    public void startServer() throws Exception {
        /**
         *  使用自定义的的server类，拦截错误信息
         */
        WebServer webServer = new WebServer(port);
        ServletContextHandler requestHandle = getRequestHandle();

        NCSARequestLog requestLog = new NCSARequestLog(logPath);
        requestLog.setAppend(true);
        requestLog.setExtended(false);
        requestLog.setLogTimeZone("GMT");
        requestLog.setLogLatency(true);

        if (projectName != null && !projectName.startsWith("/")) {
            projectName = "/" + projectName;
        }

        /**
         * 注意，紧接在后面的路径，不能以projectName的值开头，否则有可能会出现问题
         */
        requestHandle.setContextPath(projectName);
        webServer.setRequestLog(requestLog);
        webServer.setHandler(requestHandle);
        webServer.setErrorHandler(new DefaultResourceNotFoundHandlerServlet());
        webServer.start();
    }

    /**
     * 构造核心处理链
     */
    private ServletContextHandler getRequestHandle(){
        ServletContextHandler requestHandle = new ServletContextHandler();
        requestHandle.setResourceBase(resourceBasePath + "/");

        IClassFinder classFinder = new ControllerFinder();
        IClassLoader classLoader = new AutoClassLoader(classFinder);

        ApiUrlHandleServlet apiServlet = new ApiUrlHandleServlet(controllerPackets, "/api",
                                                                 classLoader, isUrlMappingCheck);
        apiServlet.setErrorHandler(new ApiErrorHandler());

        /**
         * session控制器，负责向浏览器推送session信息
         */
        SessionHandler sessionHandler = new SessionHandler();
        sessionHandler.setHttpOnly(true);
        sessionHandler.setSecureRequestOnly(true);
        requestHandle.setSessionHandler(sessionHandler);

        FreemarkerHandleServlet freemarkerServlet = new FreemarkerHandleServlet(
            resourceBasePath + "/", false);
        DefaultServlet staticRescourceServlet = new DefaultServlet();

        /**
         * 设置文件索引器的起始目录，用于文件索引，前端js自动引用
         */
        FileListDirective.setBasePath(resourceBasePath);
        /**
         * XSS攻击防护
         */
        requestHandle.addFilter(XSSCheckFilter.class, "/*", EnumSet.allOf(DispatcherType.class));
        /**
         * 请求工具类支持
         */
        requestHandle.addFilter(WebContextFilter.class, "/*", EnumSet.allOf(DispatcherType.class));
        /**
         * js脚本静态资源处理器，这里的路径要和basePath能接上，下同
         */
        requestHandle.addServlet(new ServletHolder(staticRescourceServlet), "/webResource/js/*");
        /**
         * css样式静态资源处理器，这里的路径要和basePath能接上，下同
         */
        requestHandle.addServlet(new ServletHolder(staticRescourceServlet), "/webResource/css/*");
        /**
         * log日志静态资源处理器，这里的路径要和basePath能接上，下同
         */
        requestHandle.addServlet(new ServletHolder(staticRescourceServlet), "/webResource/log/*");
        /**
         * img图片静态资源处理器，这里的路径要和basePath能接上，下同
         */
        requestHandle.addServlet(new ServletHolder(staticRescourceServlet), "/webResource/image/*");
        /**
         * 接口处理器
         */
        requestHandle.addServlet(new ServletHolder(apiServlet), "/api/*");
        /**
         * freemarker页面处理器
         */
        requestHandle.addServlet(new ServletHolder(freemarkerServlet), "/webResource/view/*");

        for (WebFilter<?> filter : filters) {

            Filter filterInstance = filter.getFilterInstance();
            FilterHolder filterHolder;

            if (filterInstance != null) {
                filterHolder = new FilterHolder(filterInstance);
            }
            else {
                filterHolder = new FilterHolder(filter.getFilter());
            }

            if(filter.getInitParams() != null){
                filterHolder.setInitParameters(filter.getInitParams());
            }

            requestHandle.addFilter(filterHolder, filter.getPath(),
                                    EnumSet.allOf(DispatcherType.class));
        }

        for (WebServlet servlet : servlets) {
            requestHandle.addServlet(new ServletHolder(servlet.getServlet()), servlet.getPath());
        }

        return requestHandle;
    }

    private void setProjectName(String projectName) {
        if (projectName != null && !projectName.startsWith("/")) {
            projectName = "/" + projectName;
        }
        this.projectName = projectName;
    }

}
